function lt(title = '分隔符') {
    console.info(`===============${title}====================`)
}

lt('类型转换')

// js 的 类型转换
let num = 1
let str = 'str'
let True = true
let False = false
let Undf = undefined
let Sym = Symbol('a')
let obj = {}

const expData = {
    Number: 1,
    String: 'str',
    Boolean_True: true,
    Boolean_False: false,
    Undefined: undefined,
    Symbol: Symbol('a'),
    Object: {}
}
let tb = {
    Boolean: {},
    Number: {},
    String: {},
    Object: {}
}

// 转换类型
for (const key in expData) {
    if (Object.hasOwnProperty.call(expData, key)) {
        const element = expData[key]
        try {
            tb['Boolean'][key] = Boolean(element)
        } catch (error) {
            tb['Boolean'][key] = '-'
        }
        try {
            tb['Number'][key] = Number(element)
        } catch (error) {
            tb['Number'][key] = '-'
        }
        try {
            tb['String'][key] = String(element)
        } catch (error) {
            tb['String'][key] = '-'
        }
        try {
            tb['Object'][key] = Object(element)
        } catch (error) {
            tb['Object'][key] = '-'
        }
    }
}
console.table(tb)

// string 转 number
lt('String 2 Number')
let Strs = [
    '30',
    '0b111',
    '0o13',
    '0xFF',
    '2e6', // 还支持使用科学计数法 用e 来表示
    '1e2', // 还支持使用科学计数法 用e 来表示
    '-1e-3'// 还支持使用科学计数法 用e 来表示
]
// P.S.1 parseInt 和 parseFloat 并不使用这个转换，所以支持的语法跟这里不尽相同。
// P.S.2 在不传入第二个参数的情况下，parseInt 只支持 16 进制前缀“0x”，而且会忽略非数字字符，也不支持科学计数法。
// 在一些古老的浏览器环境中，parseInt 还支持 0 开头的数字作为 8 进制前缀 。
Strs.forEach(ele => {
    console.log(ele, Number(ele), parseInt(ele), parseFloat(ele))
})

lt('Number 2 String')
// Number 2 String在较小的范围内，数字到字符串的转换是完全符合你直觉的十进制表示。
// 当 Number 绝对值较大或者较小时，字符串表示则是使用科学计数法表示的。
let Nums = [
    0,
    0.000000000000000001, // 绝对值较大或者较小时，字符串表示则是使用科学计数法表示的。
    0x23332  // 绝对值较大或者较小时，字符串表示则是使用科学计数法表示的。
]
Nums.forEach(num => {
    console.log(num, String(num))
})

lt('装箱转换')
// Symbol 函数无法使用 new 来调用，但是可以利用一个装箱的机制来得到一个 装箱转换 
// Symbol 对象，可以 利用一个函数的 call 方法来强迫产生装箱 
var symObj1 = (function () { return this }).call(Symbol("a"))

console.log(typeof symObj1) //object
console.log(symObj1 instanceof Symbol) //true
console.log(symObj1.constructor == Symbol) //true
lt()
// 使用内置的 Object 函数，我们可以在javaScript 代码中 显式的调用装箱能力
var symObj2 = Object(Symbol("b"))

console.log(typeof symObj2) //object
console.log(symObj2 instanceof Symbol) //true
console.log(symObj2.constructor == Symbol) //true
lt()
// 每一类装箱对象皆有私有的 Class 属性，这些属性可以用 Object.prototype.toString 获取：
const classInfoTable = {}
for (const key in expData) {
    classInfoTable[key] = {}
    if (Object.hasOwnProperty.call(expData, key)) {
        const element = expData[key]
        // Object.prototype.toString 是可以准确识别对象对应的基本类型的方法，它比 instanceof 更加准确。
        // 但需要注意的是，call 本身会产生装箱操作，所以需要配合 typeof 来区分基本类型还是对象类型。
        classInfoTable[key]['toString'] = Object.prototype.toString.call(element)
        classInfoTable[key]['typeof'] = typeof element
    }
}
console.table(classInfoTable)


lt('拆箱转换')
// 在 JavaScript 标准中，规定了 ToPrimitive 函数，它是对象类型到基本类型的转换（即，拆箱转换）。

// 我们定义了一个对象 obj
var obj = {
    // obj 有 valueOf 和 toString 两个方法，这两个方法都返回一个对象
    valueOf: () => {
        console.log("valueOf")
        return {}
    },
    toString: () => {
        console.log("toString")
        return {}
    }
}
// try {
//     lt(' obj * 1 ')
//     obj * 1 // 你会看见先执行了 valueOf，接下来是 toString，最后抛出了一个 TypeError 这就说明了这个拆箱转换失败了。
// } catch (error) {
//     lt('String()')
//     String(obj) //使用 String(obj) 执行顺序发生了改变， 先执行 toString再执行了valueOf ,最后抛出了一个 TypeError 。
// }

// 在 ES6 之后，还允许对象通过显式指定 @toPrimitive Symbol 来覆盖原有的行为。
lt('toPrimitive')
obj[Symbol.toPrimitive] = () => {
    console.log("toPrimitive")
    return "hello obj"
}
console.log(obj + "")